package biolabs.visual

import biolabs.core.{AssociativeChildSelector, BioObject, Clock}
import biolabs.sink.Sink

import java.io.Closeable

import VisualizationConfiguration._

/** Defines what {@link Visualizer}s to use for which types of objects.
  *
  * @tparam R type of visual representation the visualizers will produce.
  */
class VisualizationConfiguration[R] private (
		val method: VisualizationMethod[R],
		map: Map[AnyRef, AnyRef]) {

	def this(method: VisualizationMethod[R], items: List[Item[_, R]]) =
		this(method, { val m: Map[AnyRef, AnyRef] = items.toMap; m })

	/** Defines visualization for type {@code m}.
	  * @return new updated configuration.
	  */
	def update[T](
			m: Manifest[T],
			vs: List[Visualizer[T, R]],
			augs: List[Augmentation[T, R, _]]
	): VisualizationConfiguration[R] =
		new VisualizationConfiguration(method, map + (m -> (vs, augs)))

	/** For the given object, returns the configured visualizers
	  * and visual augmentations of its children.
	  */
	def get[T](o: T)(implicit m: Manifest[T]) = map.get(m) match {
		case Some(x) => Some(x.asInstanceOf[(List[Visualizer[T, R]], List[Augmentation[T, R, _]])])
		case None => None
	}

	/** Applies this configuration to visualize the given object on every tick
	  * of the given clock and writes the visual output to the given sinks.
	  *
	  * @param obj object to visualize
	  * @param clock gives impulses to visualization
	  * @param initialTick time (simulation step) used for the initial
	  * visualization of the object (prior to any ticks from clock).
	  * If negative, no initial visualization is produced.
	  * @param sinks the sinks the visual representation will be written to.
	  * @return a handle that can be used to shut down the visualization
	  * established by this call. Calling `close()` on the return handle
	  * also closes the given sinks.
	  */
	def apply(obj: Visualizable, clock: Clock, initialTick: Long, sinks: Array[Sink[R]]): Closeable = {

		val handle = new Closeable {

			val listener = new Clock.ClockListener {
				override def tick(time: Long) {
					obj.visualize(VisualizationConfiguration.this) match {
						case Some(vis) => sinks foreach(_.push(time, vis))
						case None => // do nothing
					}
				}
				override def terminated() {
					close()
				}
			}

			override def close() {
				clock.removeClockListener(listener)
				sinks foreach(_.close())
			}
		}

		if(initialTick >= 0)
			handle.listener.tick(initialTick)

		clock.addClockListener(handle.listener)

		handle
	}

	/** Varargs version of apply(), with no initial visualization. */
	def apply(obj: Visualizable, clock: Clock, sinks: Sink[R]*): Closeable =
		apply(obj, clock, -1, sinks.toArray)

	/** Varargs version of apply(), with initial
	  * visualization at the given artificial tick.
	  */
	def apply(obj: Visualizable, clock: Clock, initialTick: Long, sinks: Sink[R]*): Closeable =
		apply(obj, clock, initialTick, sinks.toArray)
}

/** Contains classes that constitute an internal DSL for visualization
  * configuration. Given a [[biolabs.visual.VisualizationMethod]], say,
  * [[biolabs.visual.j2d.Java2D]], the simplest use is:
  *
  * $T    $Java2D $configure `(`$visualize `[`$MyObj`]` $using ''visualizer''`)`
  *
  * where ''visualizer'' is of type [[biolabs.visual.Visualizer]]`[`$MyObj, [[biolabs.visual.j2d.Java2DPainter]]`]`.
  *
  *
  * Multiple visualizers can be specified for one type of objects:
  *
  * $T    $Java2D $configure `(`$visualize `[`$MyObj`]` $using `(`''visualizer1, visualizer2''`))`
  *
  *
  * Specifying augmentation of visualizations of some groups of children:
  *
  * $T    $Java2D $configure `(`$visualize `[`$MyObj`]` $using ''visualizer'' $augment $children1 $by ''fn1'' $augment $children2 $by `(`''fn2, fn3''`))`
  *
  * where ''MyObject.children*'' are of type
  * [[biolabs.core.AssociativeChildSelector]]`[`''MyObject'', _, A`]`
  * and ''fn*'' are of type `(Java2DPainter, A) => Java2DPainter`.
  *
  *
  * Configuring visualization for multiple types of objects:
  *
  * $T    $Java2D $configure `(` $br
  * $T$T      $visualize `[`$MyObj`]` $using ''visualizer1'' $augment $children $by ''fn'', $br
  * $T$T      $visualize `[`$OtherObj`]` $using `(`''visualizer2'', ''visualizer3''`)` $br
  * $T    `)`
  *
  * @define T &nbsp;&nbsp;&nbsp;&nbsp;
  * @define Java2D `Java2D`
  * @define configure '''`configure`'''
  * @define visualize '''`visualize`'''
  * @define using '''`using`'''
  * @define augment '''`augment`'''
  * @define by '''`by`'''
  * @define MyObj ''MyObject''
  * @define children ''MyObject.children''
  * @define children1 ''MyObject.children1''
  * @define children2 ''MyObject.children2''
  * @define OtherObj ''AnotherObject''
  * @define br <br/>
  */
object VisualizationConfiguration {

	/** An entry point for configuring visualization of
	  * one type of objects using the above internal DSL.
	  */
	def visualize[T](implicit m: Manifest[T]): StageTypeSpecified[T] =
		StageTypeSpecified(m)

	/** ''Augments'' a value with additional data.
	  * Takes a value and additional data and returns a
	  * new value, an ''augmentation'' of the old one.
	  *
	  * @tparam V type of the augmented value
	  * @tparam A type of data used for augmentation
	  */
	type AugmentingFunction[V, A] = (V, A) => V

	/** Specifies visualization for one type of objects. */
	type Item[T, R] = (Manifest[T], (List[Visualizer[T, R]], List[Augmentation[T, R, _]]))

	/** Stage of defining a configuration item that represents
	  * a valid configuration for one type of objects, and thus
	  * is convertible to a configuration item.
	  */
	trait ValidStage[T, R] {
		def toConfigurationItem(): Item[T, R]
	}

	/** Implicit conversion from a stage of defining configuration item
	  * to an actual configuration item.
	  */
	implicit def stageToItem[T, R](s: ValidStage[T, R]) = s.toConfigurationItem()

	/** Configuration item definition in progress, after the type of object
	  * for which configuration is going to be defined has been specified.
	  */
	case class StageTypeSpecified[T](m: Manifest[T]) {

		def using[R](v: Visualizer[T, R], vs: Visualizer[T, R]*): StageVisualizersSpecified[T, R] =
			StageVisualizersSpecified(m, v::vs.toList)
	}

	/** Configuration item definition in progress, after the list
	  * of visualizers for the given type has been specified.
	  */
	case class StageVisualizersSpecified[T, R](
			m: Manifest[T],
			vs: List[Visualizer[T, R]],
			augs: List[Augmentation[T, R, _]] = Nil
	) extends ValidStage[T, R] {

		override def toConfigurationItem() = (m, (vs, augs.reverse))

		def augment[A](sel: AssociativeChildSelector[T, _, A]): StageAugmenting[T, R, A] =
			StageAugmenting(this, sel)

		private[visual] def +(aug: Augmentation[T, R, _]) =
			StageVisualizersSpecified(m, vs, aug::augs)
	}

	/** Configuration definition in progress, after children
	  * to be augmented have been specified.
	  */
	case class StageAugmenting[T, R, A](
			prevStage: StageVisualizersSpecified[T, R],
			selector: AssociativeChildSelector[T, _, A]) {

		def by(aug: AugmentingFunction[R, A], augs: AugmentingFunction[R, A]*): StageVisualizersSpecified[T, R] =
			prevStage + Augmentation(selector, aug::augs.toList)
	}

	/** Designates augmentation of visual
	  * representations of selected children.
	  */
	case class Augmentation[T, R, A](
			sel: AssociativeChildSelector[T, _, A],
			augmenters: List[AugmentingFunction[R, A]])
}
